package com.greendizer.api.client;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.xml.crypto.dsig.SignatureMethod;

import com.greendizer.api.resource.seller.Email;
import com.greendizer.api.resource.seller.Invoice;
import com.greendizer.api.resource.seller.Seller;


/**
 * API client for sellers.
 */
public class SellerClient extends AbstractClient<Invoice, Email, Seller> {

  private boolean usingSignature;
  private PrivateKey privateKey;
  private PublicKey publicKey;
  private String signatureSpecification;

  /**
   * Creates a basic HTTP authentication API seller client with the given user email and password.
   * @param email User email.
   * @param password User password.
   */
  public SellerClient(String email, String password) {
    super(email, password);
    user = new Seller(this);
  }

  /**
   * Creates a TLS authentication API seller client with the given token.
   * @param token TLS token.
   */
  public SellerClient(String token) {
    super(token);
    user = new Seller(this);
  }

  /**
   * Tells whether the current seller client uses XMLi signing or not.
   * @return True if the current seller client uses signing, false otherwise.
   */
  public boolean usingSignature() {
    return usingSignature;
  }

  /**
   * Gets this seller client private key.
   * @return This seller client private key.
   */
  public PrivateKey getPrivateKey() {
    return privateKey;
  }

  /**
   * Gets this seller client public key.
   * @return This seller client public key.
   */
  public PublicKey getPublicKey() {
    return publicKey;
  }

  /**
   * Gets this seller client keys signature method specification.
   * @return This seller client keys signature method specification.
   */
  public String getSignatureSpecification() {
    return signatureSpecification;
  }

  /**
   * Installs the given private and public keys byte flows to the current seller client.
   * These byte flows should be in the binary DER format (and not the popular OpenSSL ASCII PEM format).
   * For more information, please read this interesting <a href="http://codeartisan.blogspot.com/2009/05/public-key-cryptography-in-java.html">article</a>.
   * @param privateKeyBytes The private key byte flow.
   * @param publicKeyBytes The public key byte flow.
   */
  public void installKeys(byte[] privateKeyBytes, byte[] publicKeyBytes) {
    boolean installed = false;
    for (GDSignatureMethod signatureMethod : GDSignatureMethod.values()) {
      try {
        KeyFactory keyFactory = KeyFactory.getInstance(signatureMethod.toString());
        privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
        publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
        signatureSpecification = signatureMethod.getSpecification();
        usingSignature = true;
        installed = true;
      }
      catch (NoSuchAlgorithmException e) {
        //hum, again: 1 + 1 = ?
      } catch (InvalidKeySpecException e) {
        continue;
      }
    }
    if (!installed) {
      throw new RuntimeException("Unabled to install given keys using supported signature methods (RSA & DSA).");
    }
  }

  /**
   * Installs the private and public from the given files paths.
   * @param privateKeyPath The private key file path.
   * @param publicKeyPath The public key file path.
   * @throws IOException If an error occurred handling files.
   */
  public void installKeys(String privateKeyPath, String publicKeyPath) throws IOException {
    installKeys(readFileBytes(privateKeyPath), readFileBytes(publicKeyPath));
  }

  private static byte[] readFileBytes(String filePath) throws IOException {
    File file = new File(filePath);
    DataInputStream input = new DataInputStream(new FileInputStream(file));
    byte[] bytes = new byte[(int) file.length()];
    input.readFully(bytes);
    input.close();
    return bytes;
  }

  private enum GDSignatureMethod {

    RSA   (SignatureMethod.RSA_SHA1),
    DSA   (SignatureMethod.DSA_SHA1),
    ;

    private String specification;

    private GDSignatureMethod(String specification) {
      this.specification = specification;
    }

    private String getSpecification() {
      return specification;
    }
  }
}
